home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / archival / mirror-2.1 / mm < prev    next >
Encoding:
Text File  |  1993-06-28  |  9.7 KB  |  465 lines

  1. #!/usr/bin/perl
  2. # Mirror Master.
  3. # Run several mirrors in parallel.
  4. #
  5. # By Lee McLoughlin <lmjm@doc.ic.ac.uk>
  6. #  You can do what you like with this except claim that you wrote it or
  7. #  give copies with changes not approved by Lee.  Neither Lee nor any other
  8. #  organisation can be held liable for any problems caused by the use or
  9. #  storage of this package.
  10. #
  11. # $Id: mm,v 2.1 1993/06/28 15:21:28 lmjm Exp lmjm $
  12. # $Log: mm,v $
  13. # Revision 2.1  1993/06/28  15:21:28  lmjm
  14. # Full 2.1 release
  15. #
  16. #
  17.  
  18. # Args:
  19. # -opattern        - limit to site:packages matching pattern
  20. # -t            - ignore timers
  21. # -debug        - increase debugging level(-debug -debug =more debugging)
  22. # -s            - turn on process entry/exit debugging
  23.  
  24. # mm input looks like:
  25. # home=directory    - where to work from
  26. # max=N            - max. no. of parallel mirrors
  27. # mirror=command    - how to call mirror
  28. # skip=site:package    - skip this site:package when you come across it
  29. # cmd=command        - Run this command now.
  30. # cmdin=command        - Run this command and use its output as mm input
  31. # site:package min-restart-last-ok min-restart-last-notok mirror args
  32. # EXIT            - skip rest of current file
  33.  
  34.  
  35. # Defaults
  36. # Max mirrors to run at the same time
  37. $max = 6;
  38.  
  39. # In $mirror the $args, $package and $site fields are replaced with
  40. # fields from the package entry in the mm input files.
  41. # $pkg is the package number fixed up to replace characters likely to give
  42. # grief under unix.
  43. # This expects the directory logs to already exist.
  44. $mirror = "exec ./mirror \$args -p'\$package' packages/\$site > logs/\$site:\$pkg 2>&1";
  45.  
  46. $status_file = 'mm.status';
  47.  
  48. # used as a file handle.
  49. $fileno = 'fd00';
  50.  
  51. $running = 0;
  52.  
  53. # Really should share these properly with mirror
  54. # "#defines" for the above
  55. $exit_xfers = 16;  # Add this to the exit code to show xfers took place
  56. $exit_ok = 0;
  57. $exit_fail = 1;
  58. $exit_fail_noconnect = 2;
  59.  
  60. # Used in the status file to mark a site:package locked
  61. $locked = 'l';
  62. $unlocked = 'u';
  63.  
  64. $secs_per_hour = 60 * 60;
  65.  
  66.  
  67. # Parse arguments
  68. while( $#ARGV >= 0 ){
  69.     local( $arg ) = shift;
  70.  
  71.     # only both with -flag's
  72.     if( $arg !~ /^-/ ){
  73.         unshift( ARGV, $arg );
  74.         last;
  75.     }
  76.  
  77.     if( $arg =~ /-o(.*)/ ){
  78.         # Only for these site:packages
  79.         $only = $1;
  80.     }
  81.     elsif( $arg =~ /-t/ ){
  82.         $ignore_timers = 1;
  83.     }
  84.     elsif( $arg =~ /-debug/ ){
  85.         $debug++;
  86.         $| = 1;
  87.     }
  88.     elsif( $arg =~ /-s/ ){
  89.         $status_debug = 1;
  90.         $| = 1;
  91.     }
  92.     else {
  93.         # Pass any unknown args down to mirror
  94.         $extra_args .= ' ' . $arg;
  95.     }
  96. }
  97.  
  98. # Wish I could think of a better way of doing this!
  99. while( $#ARGV >= 0 ){
  100.     &parse_file( shift );
  101.     $parsed = 1;
  102. }
  103. if( ! $parsed ){
  104.     &parse_file( '-' );
  105. }
  106.  
  107. &wait_till_done( 0 );
  108.  
  109. sub parse_file
  110. {
  111.     local( $file ) = @_;
  112.     local( $fd, $closeit );
  113.     
  114.     if( $debug > 1){
  115.         print "parse_file( $file )\n";
  116.     }
  117.  
  118.     if( $file eq '-' ){
  119.         $fd = 'STDIN';
  120.         $closeit = 0;
  121.     }
  122.     else {
  123.         $fd = $fileno++;
  124.         if( ! open( $fd, $file ) ){
  125.             die "Cannot open $file";
  126.         }
  127.         $closeit = 1;
  128.     }
  129.         
  130.     while( <$fd> ){
  131. #        print "$fd: ",$_ if $debug;
  132.         next if /^#/ || /^\s*$/;
  133.         
  134.         chop;
  135.         
  136.         # Skip rest of input.
  137.         if( /^EXIT$/ ){
  138.             last;
  139.         }
  140.     
  141.         if( /^home\s*=\s*(\S+)/ ){
  142.             chdir( $1 ) || die "Cannot chdir to $1";
  143.             next;
  144.         }
  145.     
  146.         if( /^max\s*=\s*(\d+)/ ){
  147.             # Set the max no. of parallel mirrors
  148.             $max = $1;
  149.             next;
  150.         }
  151.         
  152.         if( /^mirror\s*=\s*(.*)/ ){
  153.             # Set the mirror command
  154.             $mirror = $1;
  155.             next;
  156.         }
  157.         
  158.         if( /^cmd\s*=\s*(.*)/ ){
  159.             # Run this shell command now
  160.             # Use it at the start of scripts to do cleanups and
  161.             # at the end to email logs
  162.             system( $1 );
  163.             next;
  164.         }
  165.         
  166.         if( /^cmdin\s*=\s*(.*)/ ){
  167.             # Run this command and use its output as mm input
  168.             # (The trailing hash makes open treat it as a command.
  169.             &parse_file( $1 . '|' );
  170.             next;
  171.         }
  172.         
  173.         if( /^skip\s*=\s*(.*)/ ){
  174.             # Skip this site:package
  175.             push( @skips, $1 );
  176.             next;
  177.         }
  178.     
  179.         # Must be a job to run
  180.         # site:package min-restart-last-ok min-restart-last-notok mirror-args
  181.         if( /^(.+):(.+)\s+(\d+)\s+(\d+)(\s*)?(.*)?/ ){
  182.             local( $site, $package, $min_ok, $min_notok, $args )
  183.                 = ($1, $2, $3, $4, $6);
  184.             $pkg = &fix_package( $package );
  185.             local( $site_package ) = "$site:$package";
  186.     
  187.             if( $site_package =~ /'/ ){
  188.                 warn "site/package name must not contain a prime ('), skipping: $site:$package\n";
  189.                 next;
  190.             }
  191.             
  192.             # Is this a skipped site?
  193.             if( grep( /$site_package/, @skips ) ){
  194.                 print "skipping $site_package, in skip list\n" if( $debug );
  195.                 next;
  196.             }
  197.     
  198.             # If restricting the packages to look at skip all that
  199.             # don't match.
  200.             if( $only && $site_package !~ /$only/ ){
  201.                 print "skipping $site_package, not in $only\n" if( $debug );
  202.                 next;
  203.             }
  204.     
  205.             # Only try the first instance of a site:package found.
  206.             next if $already{ $site_package };
  207.             $already{ $site_package } = 1;
  208.             
  209.             if( ! &ok_to_restart( $site_package, $min_ok, $min_notok ) ){
  210.                 next;
  211.             }
  212.             local( $command ) = "$mirror";
  213.             local( $a ) = "$args $extra_args";
  214.             $command =~ s/\$args/$a/g;
  215.             $command =~ s/\$site/$site/g;
  216.             $command =~ s/\$package/$package/g;
  217.             $command =~ s/\$pkg/$pkg/g;
  218.             &run( $command, $site_package );
  219.             next;
  220.         }
  221.         else {
  222.             warn "Cannot parse, so skipping: $_\n";
  223.         }
  224.     }
  225.     
  226.     if( $closeit ){
  227.         close( $fd );
  228.     }
  229. }
  230.  
  231. sub run
  232. {
  233.     local( $command, $site_package ) = @_;
  234.     
  235.     if( $running >= $max ){
  236.         &wait_till_done( 1 );
  237.     }
  238.  
  239.     local( $pid ) = &spawn( $command );
  240.     $running ++;
  241.     $procs{ $pid } = $site_package;
  242.     print "$pid: $procs{ $pid } started: $command\n" if $debug;
  243.     &upd_status( $site_package, time, 0, $locked, $pid );
  244. }
  245.  
  246. sub spawn
  247. {
  248.     local( $command ) = @_;
  249.     local( $id ) = fork();
  250.     
  251.     if( $id == 0 ){
  252.         # This is the child
  253.         exec( $command );
  254.         die "Failed to exec $command";
  255.     }
  256.     elsif( $id > 0 ){
  257.         # This is the parent
  258.         return $id;
  259.     }
  260.     
  261.     die "Failed to fork";
  262.     # Should really sleep and try again...
  263. }
  264.  
  265. sub wait_till_done
  266. {
  267.     local( $children ) = @_;
  268.     local( $pid );
  269.     
  270.     if( $children == 0 ){
  271.         # Wait for all remaining children
  272.         while( ($pid = wait()) != -1 ){
  273.             &proc_end( $pid, $? );
  274.         }
  275.     }
  276.     else {
  277.         # Wait for the next child to finish
  278.         $pid = wait();
  279.         if( $pid == -1 ){
  280.             die "Waiting for NO children";
  281.         }
  282.         &proc_end( $pid, $? );
  283.     }
  284. }
  285.  
  286. # A process has terminate.   Figure out which one and update the status file
  287. sub proc_end
  288. {
  289.     local( $pid, $status ) = @_;
  290.     local( $site_package ) = $procs{ $pid };
  291.     
  292.     if( $site_package !~ /(.+):(.+)/ ){
  293.         # Ignore these.  It is probably just an open(..,"..|)
  294.         # terminating.  They seem to do it at odd times!
  295.         return;
  296.     }
  297.     
  298.     print "$pid: $site_package terminated[$status]\n" if $debug;
  299.     $running --;
  300.  
  301.     &upd_status( $site_package, time, $status, $unlocked );
  302. }
  303.  
  304. sub ok_to_restart
  305. {
  306.     local( $site_package, $min_ok, $min_notok ) = @_;
  307.     
  308.     local( $last_tried, $status, $lock, $pid ) = &get_status( $site_package );
  309.     
  310.     if( $lock eq $locked ){
  311.         # Does the process that locked it still exist?
  312.         if( kill( 0, $pid ) ){
  313.             warn "Not trying $site_package: locked by $pid\n";
  314.             return 0;
  315.         }
  316.     }
  317.     
  318.     if( $ignore_timers ){
  319.         return 1;
  320.     }
  321.  
  322.     $min_ok = $min_ok * $secs_per_hour;
  323.     $min_notok = $min_notok * $secs_per_hour;
  324.     
  325.     local( $min ) = $min_notok;
  326.     if( $status & $exit_ok ){
  327.         $min = $min_ok;
  328.     }
  329.  
  330.     local( $now ) = time;
  331.     local( $togo ) = ($last_tried + $min) - $now;
  332.     if( $last_tried && $togo > 0 ){
  333.         warn "Not trying $site_package: $togo seconds to go\n";
  334.         return 0;
  335.     }
  336.     
  337.     return 1;
  338. }
  339.  
  340. sub lock_status
  341. {
  342.     flock( status, $LOCK_EX );
  343. }    
  344.  
  345. sub unlock_status
  346. {
  347.     flock( status, $LOCK_UN );
  348. }
  349.  
  350. # Update the status file
  351. sub upd_status
  352. {
  353.     local( $site_package, $last_tried, $status, $lock, $pid ) = @_;
  354.     
  355.     # Make sure a status file exists
  356.     if( ! -e $status_file ){
  357.         open( status, ">$status_file" ) || die "Cannot create $status_file";
  358.         close( status );
  359.     }
  360.  
  361.     # Suck in the status file
  362.     open( status, '+<' . $status_file ) || die "Cannot open $status_file";
  363.     &lock_status();
  364.     seek( status, 0, 0 );
  365.     $upd = 0;
  366.     local( @new ) = ();
  367.     while( <status> ){
  368.         if( /^(.+:.+)\s+(\d+)\s+(\d+)\s+($locked|$unlocked)\S?\s+(\d+)$/ ){
  369.             local( $sp, $lt, $st, $lk, $p ) =
  370.                 ($1, $2, $3, $4, $5);
  371.             if( $sp eq $site_package ){
  372.                 print "upd: $_" if( $status_debug );
  373.                 if( $last_tried ){
  374.                     $lt = $last_tried;
  375.                 }
  376.                 if( $status ){
  377.                     $st = $status;
  378.                 }
  379.                 if( $lock ){
  380.                     $lk = $lock;
  381.                 }
  382.                 if( $pid > 0 ){
  383.                     $p = $pid;
  384.                 }
  385.                 $upd++;
  386.                 push( @new, "$sp $lt $st $lk $p\n" );
  387.                 print "$sp $lt $st $lk $p\n" if( $status_debug );
  388.                 next;
  389.             }
  390.             push( @new, $_ );
  391.         }
  392.         elsif( /^\s*$/ ){
  393.             last;
  394.         }
  395.         else {
  396. #            warn "Unknown input skipping rest of file, $status_file:$.: $_\n";
  397.             last;
  398.         }
  399.     }
  400.     if( ! $upd ){
  401.         local( $new ) = "$site_package $last_tried $status $lock $pid\n";
  402.         push( @new, $new );
  403.         print "new: $new" if( $status_debug );
  404.     }
  405.     seek( status, 0, 0 );
  406.     foreach $new ( @new ){
  407.         print status $new;
  408.     }
  409.     # Get rid of the rest of the file.
  410.     eval "truncate( status, tell( status ) )";
  411.     
  412.     &unlock_status();
  413.     close( status );
  414. }
  415.  
  416.  
  417. # Get the status of a site:package
  418. sub get_status
  419. {
  420.     local( $site_package ) = @_;
  421.     local( $last_tried, $status, $lock, $pid ) = (0, 0, ' ', -1);
  422.     
  423.     # Make sure a status file exists
  424.     if( ! -e $status_file ){
  425.         open( status, ">$status_file" ) || die "Cannot create $status_file";
  426.         close( status );
  427.     }
  428.  
  429.     # Suck in the status file
  430.     open( status, '+<' . $status_file ) || die "Cannot open $status_file";
  431.     &lock_status();
  432.     seek( status, 0, 0 );
  433.     local( @new ) = ();
  434.     while( <status> ){
  435.         if( /^(.+:.+)\s+(\d+)\s+(\d+)\s+($locked|$unlocked)\S?\s+(\d+)$/ ){
  436.             local( $sp, $lt, $st, $lk, $p ) =
  437.                 ($1, $2, $3, $4, $5);
  438.             if( $sp eq $site_package ){
  439.                 $last_tried = $lt;
  440.                 $status = $st;
  441.                 $lock = $lk;
  442.                 $pid = $p;
  443.                 print "Status: $_" if( $status_debug );
  444.                 last;
  445.             }
  446.         }
  447.         else {
  448.             warn "Unknown input skipping rest of file, $status_file:$.: $_\n";
  449.             last;
  450.         }
  451.     }
  452.     &unlock_status();
  453.     close( status );
  454.     return( $last_tried, $status, $lock, $pid );
  455. }
  456.  
  457. # Fix up a package name.
  458. # strip trailing and leading ws and replace awkward characters
  459. sub fix_package
  460. {
  461.     local( $package ) = @_;
  462.     $package =~ s:[\s/']:_:g;
  463.     return $package;
  464. }
  465.